type EventMap = Record<string, any>;

type EventKey<T extends EventMap> = string & keyof T;
type EventHandler<T> = (params: T) => void;

interface Emitter<T extends EventMap> {
  on<K extends EventKey<T>>(
    eventName: K,
    handler: EventHandler<T[K]>,
    scope?: any,
  ): void;
  off<K extends EventKey<T>>(eventName: K, fn: EventHandler<T[K]>): void;
  emit<K extends EventKey<T>>(eventName: K, params: T[K]): void;
}

interface Listener<T> {
  handler: (p: T) => void;
  scope?: any;
}

/**
 * Creates a new emitter object.
 *
 * @return {Emitter<T>} The newly created emitter object.
 */
function createEmitter<T extends EventMap>(): Emitter<T> {
  const listeners: {
    [K in keyof EventMap]: Array<Listener<EventMap[K]>>;
  } = {};

  return {
    on(key, handler, scope) {
      listeners[key] = (listeners[key] || []).concat({ handler, scope });
    },
    off(key, handler) {
      listeners[key] = (listeners[key] || []).filter(
        (l) => l.handler !== handler,
      );
    },
    emit(key, data) {
      (listeners[key] || []).forEach(function ({ handler, scope }) {
        handler.call(scope, data);
      });
    },
  };
}

export type {
  EventMap,
  EventKey,
  EventHandler as EventReceiver,
  Emitter,
  Listener,
};
export default createEmitter;
